/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package routing;

import de.uniba.wiai.lspi.chord.data.*;
import de.uniba.wiai.lspi.chord.service.*;
import de.uniba.wiai.lspi.chord.service.impl.*;
import java.io.Serializable;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.Signature;
import java.security.SignatureException;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import routing.exception.BadPortException;
import routing.exception.BadSignatureException;
import routing.exception.UserExistException;
import routing.exception.UserNotFoundException;
/**
 *
 * @author Paweł
 */
public class OpenChordWrapper {

    private static boolean propsLoaded = false;

    private Chord chord;
    private PrivateKeysStore privateKeyStore;
    private PasswordStore passwordStore;

    public OpenChordWrapper() {
        if (!propsLoaded) {
            PropertiesLoader.loadPropertyFile();
            propsLoaded = true;
        }
        privateKeyStore = new PrivateKeysStore();
        passwordStore = new PasswordStore();
    }

    /**
     * Creates new OpenChord overlay network specified port.
     * @param port
     * @throws BadPortException
     * @throws UnknownHostException
     * @throws ServiceException
     * @throws MalformedURLException
     */
    public void create(int port) throws BadPortException, UnknownHostException, ServiceException, MalformedURLException {
        chord = new ChordImpl();
        try {
            chord.create(
                new URL( // protokol://adres IP:port
                   URL.KNOWN_PROTOCOLS.get(URL.SOCKET_PROTOCOL) + // protokół seici nakładkowej
                   "://" + InetAddress.getLocalHost().getHostAddress() + // adres IP węzła tworzącego
                   ":" + port + "/" // port na którym będzie nasłuchiwane
                )
            );
        } catch(RuntimeException ex) {
            throw new routing.exception.BadPortException(ex.getMessage());
        }
    }

    /**
     * Joins exisiting OpenChord network and listens on specified port.
     * @param listeningPort
     * @param bootstrapIP
     * @param bootstrapPort
     * @throws UnknownHostException
     * @throws ServiceException
     * @throws MalformedURLException
     */
    public void join(int listeningPort, String bootstrapIP, int bootstrapPort) throws UnknownHostException, ServiceException, MalformedURLException {
        chord = new ChordImpl();
        chord.join(
            new URL( // URL węzła lokalnego
                URL.KNOWN_PROTOCOLS.get(URL.SOCKET_PROTOCOL) + // protokół seici nakładkowej
                "://" + InetAddress.getLocalHost().getHostAddress() + // adres IP węzła tworzącego
                ":" + listeningPort + "/" // port na którym będzie nasłuchiwane
             ),
             new URL( // URL węzła inicjującego
                URL.KNOWN_PROTOCOLS.get(URL.SOCKET_PROTOCOL) + // protokół seici nakładkowej
                "://" + bootstrapIP + // adres IP węzła inicjującego
                ":" + bootstrapPort + "/" // port na którym nasłuchuje węzeł inicjujący
             )
        );
    }

    /**
     * Leaves OpenChord network.
     */
    public void leave(String username) throws ServiceException, UserNotFoundException, InvalidKeyException, UserExistException {
        changeState(username, false);
        chord.leave();
    }

    public void insertUser(String username, String password)
            throws UserExistException,
                   InvalidKeyException,
                   SignatureException,
                   ServiceException {
        try {
            // sprawdzenie, czy nie ma takiego uzytkownika w sieci
            // genereacja kluczy
            final KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
            final KeyPair kp = kpg.generateKeyPair();
            // tworzenie danej uzytkownika
            chord.insert(
                    new UsernameKey(username),
                    new UserData(username, kp.getPublic(), true));
            synchronized (this) {
                privateKeyStore.insert(username, kp.getPrivate());
                passwordStore.insert(username, password);
                System.out.println(privateKeyStore.getPrivateKey(username));
            }
        } catch (UnknownHostException ex) {
            new RuntimeException(ex);
        } catch (NoSuchAlgorithmException ex) {
            new RuntimeException(ex);
        }
    }

    public void removeUser(String username) throws InvalidKeyException, ServiceException, UserNotFoundException, BadSignatureException {
        removeUser(username, null);
    }

    private void removeUser(String username, ChordCallback callback) throws InvalidKeyException, ServiceException, UserNotFoundException, BadSignatureException {
        try {
            // podpis danej
            Signature signature = Signature.getInstance("DSAwithSHA1");
            signature.initSign(privateKeyStore.getPrivateKey(username));
            signature.update(("remove").getBytes());
            byte[] sign = signature.sign();
            if (callback == null) {
                chord.remove(new UsernameKey(username), getUserData(username), sign);
                passwordStore.remove(username);
            }
            else {
                ((ChordImpl) chord).remove(new UsernameKey(username), getUserData(username), sign, callback);
            }
        } catch (SignatureException ex) {
            Logger.getLogger(OpenChordWrapper.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(OpenChordWrapper.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public UserData getUserData(String username) throws ServiceException, UserNotFoundException {
        Set<Serializable> set = chord.retrieve(new UsernameKey(username));
        if (set.isEmpty()) throw new UserNotFoundException(username);
        return (UserData) set.iterator().next();
    }

    public boolean checkPassword(String username, String password) {
        return passwordStore.check(username, password);
    }

    public void changeState(final String username, boolean online) throws ServiceException, UserNotFoundException, InvalidKeyException, UserExistException {
        try {
            // genereacja kluczy
            final KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
            final KeyPair kp = kpg.generateKeyPair();
            // tworzenie danej uzytkownika
            //synchronized (this) {
            removeUser(username, new ChordCallback() {
                public void retrieved(Key key, Set<Serializable> entries, Throwable t) {}
                public void inserted(Key key, Serializable entry, Throwable t) {}
                public void removed(Key key, Serializable entry, Throwable t) {
                    try {
                        System.out.println("user " + username + " removed");
                        chord.insert(new UsernameKey(username), new UserData(username, kp.getPublic(), true));
                        privateKeyStore.insert(username, kp.getPrivate());
                    } catch (UnknownHostException ex) {
                        Logger.getLogger(OpenChordWrapper.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (ServiceException ex) {
                        Logger.getLogger(OpenChordWrapper.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (UserExistException ex) {
                        Logger.getLogger(OpenChordWrapper.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            });
                
                System.out.println(privateKeyStore.getPrivateKey(username));
            //}
        } catch (BadSignatureException ex) {
            Logger.getLogger(OpenChordWrapper.class.getName()).log(Level.SEVERE, null, ex);

        } catch (NoSuchAlgorithmException ex) {
            new RuntimeException(ex);
        }
    }

}
